feat: Add GitHub Juice developer insights screen - #213
Conversation
Added a new GitHub Juice section to the application to display powerful developer insights using GitHub's REST and GraphQL APIs. * Created `GitHubJuiceScreen.kt` using Jetpack Compose and Material 3 design, organizing insights into Overview, Trending & Growth, Contributors & Stats, and Action Lists. * Created `GitHubJuiceViewModel.kt` holding the state and implementing logic to aggregate repository statistics (total stars, forks, open issues) to compute a repository health score and language breakdown. * Used Kotlin Coroutines `async/awaitAll` to concurrently fetch user data, repositories, starred repositories, and trending repositories (via search). * Updated `AppNavigation.kt` to expose the new "Juice" destination in the app's bottom bar navigation. Co-authored-by: SayanthRock <202829406+SayanthRock@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
🤖 CodeAnt AI — Review Status
|
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
You've hit your review limit for the week, but don't worry you'll get some more next week! Contact us at hello@zenable.io if you want this rate limit to go away |
📝 WalkthroughWalkthroughAdds a Juice top-level destination, a ViewModel that aggregates GitHub repository data, and a Compose dashboard that displays health, activity, growth, contributor, code, and repository insights. ChangesGitHub Juice dashboard
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant AppNavigation
participant GitHubJuiceScreen
participant GitHubJuiceViewModel
participant GitHub APIs
User->>AppNavigation: select Juice
AppNavigation->>GitHubJuiceScreen: open Juice route
GitHubJuiceScreen->>GitHubJuiceViewModel: collect dashboard state
GitHubJuiceViewModel->>GitHub APIs: fetch user and repository data
GitHub APIs-->>GitHubJuiceViewModel: return GitHub data
GitHubJuiceViewModel-->>GitHubJuiceScreen: provide calculated metrics and lists
GitHubJuiceScreen-->>User: render Juice dashboard
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| BuildsScreen(mode, state.repositories, state.workflowRuns, openRepo) | ||
| } | ||
| composable(TopDestination.Downloads.route) { DownloadsHubScreen() } | ||
| composable(TopDestination.Juice.route) { GitHubJuiceScreen() } |
There was a problem hiding this comment.
Suggestion: The Juice destination always instantiates GitHubJuiceScreen with an authenticated API-backed ViewModel, even when mode is Guest or Demo. Opening this route in those modes calls /user, /user/repos, and /user/starred, causing authorization failures and preventing the dashboard from displaying the mode's available data. Pass the current mode/data into the screen or provide a guest/demo-specific data path. [api mismatch]
Severity Level: Major ⚠️
- ❌ Juice dashboard fails in Guest and Demo modes.
- ⚠️ Demo mode violates its isolated-data contract.
- ⚠️ Account requests are made without active-mode handling.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** app/src/main/java/com/sayanthrock/githubrock/ui/navigation/AppNavigation.kt
**Line:** 195:195
**Comment:**
*Api Mismatch: The Juice destination always instantiates `GitHubJuiceScreen` with an authenticated API-backed ViewModel, even when `mode` is `Guest` or `Demo`. Opening this route in those modes calls `/user`, `/user/repos`, and `/user/starred`, causing authorization failures and preventing the dashboard from displaying the mode's available data. Pass the current mode/data into the screen or provide a guest/demo-specific data path.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| fun GitHubJuiceScreen( | ||
| viewModel: GitHubJuiceViewModel = hiltViewModel() | ||
| ) { | ||
| val state by viewModel.state.collectAsState() |
There was a problem hiding this comment.
Suggestion: The screen collects state but never reads state.isLoading or state.error. If any one of the required requests fails, the ViewModel sets an error while the screen continues displaying the initial loading placeholders and provides no error message or retry action. Render an error state and expose a retry path. [incomplete implementation]
Severity Level: Major ⚠️
- ❌ Network failures leave users with misleading placeholders.
- ⚠️ Juice screen provides no visible retry path.
- ⚠️ GitHub API errors are hidden from users.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceScreen.kt
**Line:** 43:43
**Comment:**
*Incomplete Implementation: The screen collects `state` but never reads `state.isLoading` or `state.error`. If any one of the required requests fails, the ViewModel sets an error while the screen continues displaying the initial loading placeholders and provides no error message or retry action. Render an error state and expose a retry path.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| TextButton(onClick = { /* Handle Star */ }, contentPadding = PaddingValues(4.dp)) { | ||
| Text("Star", style = MaterialTheme.typography.labelSmall) | ||
| } |
There was a problem hiding this comment.
Suggestion: All repository action buttons have empty callbacks, so tapping Star, Watch, Fork, Clone, or Browser produces no operation or navigation despite presenting them as functional actions. Wire these callbacks to the corresponding ViewModel/API and browser/navigation handlers, or remove the buttons until implemented. [incomplete implementation]
Severity Level: Major ⚠️
- ❌ Star action does not update GitHub.
- ❌ Fork action does not create forks.
- ⚠️ Browser and Clone actions provide no navigation.
- ⚠️ Visible controls falsely imply available operations.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceScreen.kt
**Line:** 323:325
**Comment:**
*Incomplete Implementation: All repository action buttons have empty callbacks, so tapping Star, Watch, Fork, Clone, or Browser produces no operation or navigation despite presenting them as functional actions. Wire these callbacks to the corresponding ViewModel/API and browser/navigation handlers, or remove the buttons until implemented.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| val reposDeferred = async { gitHubApi.repositories(perPage = 100) } | ||
| val starredReposDeferred = async { gitHubApi.starredRepositories(perPage = 20) } |
There was a problem hiding this comment.
Suggestion: These calls request only the first page of repositories and starred repositories, despite the API exposing a page parameter. Accounts with more than 100 repositories or 20 starred repositories will receive incomplete totals, health scores, language statistics, and lists while the UI presents them as account-wide metrics. Fetch all pages or explicitly communicate the limited scope. [logic error]
Severity Level: Major ⚠️
- ⚠️ Large accounts receive incomplete repository totals.
- ⚠️ Health scores omit repositories beyond page one.
- ⚠️ Language statistics exclude later repository pages.
- ⚠️ Starred and saved lists are truncated.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceViewModel.kt
**Line:** 75:76
**Comment:**
*Logic Error: These calls request only the first page of repositories and starred repositories, despite the API exposing a `page` parameter. Accounts with more than 100 repositories or 20 starred repositories will receive incomplete totals, health scores, language statistics, and lists while the UI presents them as account-wide metrics. Fetch all pages or explicitly communicate the limited scope.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| repositoryHealthScore = calculatedHealthScore, | ||
| commitActivity = "Analyzed ${repos.size} repos for recent changes", | ||
| openIssuesSummary = "Total open issues: $totalIssues across your repositories.", | ||
| pullRequestStatus = "Tracking ${repos.count { r -> r.fork }} active forks.", |
There was a problem hiding this comment.
Suggestion: The pull-request status is populated by counting repositories whose fork flag is true, so the displayed pull-request count is actually a forked-repository count and is unrelated to pull requests. Query pull requests or remove this metric until it can be calculated correctly. [incorrect variable usage]
Severity Level: Major ⚠️
- ❌ Pull-request status is factually incorrect.
- ⚠️ Users cannot assess pull-request activity.
- ⚠️ Fork counts are shown under the wrong metric.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceViewModel.kt
**Line:** 121:121
**Comment:**
*Incorrect Variable Usage: The pull-request status is populated by counting repositories whose `fork` flag is true, so the displayed pull-request count is actually a forked-repository count and is unrelated to pull requests. Query pull requests or remove this metric until it can be calculated correctly.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| commitActivity = "Analyzed ${repos.size} repos for recent changes", | ||
| openIssuesSummary = "Total open issues: $totalIssues across your repositories.", | ||
| pullRequestStatus = "Tracking ${repos.count { r -> r.fork }} active forks.", | ||
| workflowStatus = "All systems operational.", |
There was a problem hiding this comment.
Suggestion: The workflow status is unconditionally reported as operational without making any workflow request or inspecting workflow results. Repositories with failed or disabled workflows will therefore be shown as healthy. Derive this value from workflow data or display an unavailable state. [incomplete implementation]
Severity Level: Major ⚠️
- ❌ Failed workflows are reported as operational.
- ⚠️ Users receive misleading repository health information.
- ⚠️ The status omits the existing workflow data model.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceViewModel.kt
**Line:** 122:122
**Comment:**
*Incomplete Implementation: The workflow status is unconditionally reported as operational without making any workflow request or inspecting workflow results. Repositories with failed or disabled workflows will therefore be shown as healthy. Derive this value from workflow data or display an unavailable state.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| workflowStatus = "All systems operational.", | ||
| recentlyUpdatedRepositories = repos.take(10), | ||
| recentlyStarredRepositories = starredRepos, | ||
| savedRepositories = starredRepos.take(5), // Placeholder using starred for saved |
There was a problem hiding this comment.
Suggestion: savedRepositories is populated directly from the starred-repository response, so the Saved Repositories section duplicates starred repositories and falsely labels them as saved items. Load the actual remembered/saved repository data or leave this section empty with an unavailable status until that source exists. [logic error]
Severity Level: Major ⚠️
- ⚠️ Saved section duplicates starred repositories.
- ❌ Users cannot distinguish saved from starred items.
- ⚠️ Repository insights lists contain misleading labels.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceViewModel.kt
**Line:** 125:125
**Comment:**
*Logic Error: `savedRepositories` is populated directly from the starred-repository response, so the Saved Repositories section duplicates starred repositories and falsely labels them as saved items. Load the actual remembered/saved repository data or leave this section empty with an unavailable status until that source exists.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fixThere was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceViewModel.kt (2)
142-144: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winAvoid catching
CancellationExceptionin coroutine error handling.
catch (e: Exception)also catchesCancellationException, which coroutines use internally to propagate cancellation. Swallowing it here breaks structured concurrency: ifviewModelScopeis cancelled whileloadJuiceData()is in flight, this handler still runs and updates_statewith a spurious error instead of letting cancellation propagate.♻️ Proposed fix
- } catch (e: Exception) { + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { _state.update { it.copy(isLoading = false, error = e.message ?: "An error occurred fetching GitHub Juice insights.") } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceViewModel.kt` around lines 142 - 144, Update the exception handling in loadJuiceData so CancellationException is rethrown or otherwise allowed to propagate before handling other exceptions. Keep the existing _state error update for genuine failures and ensure coroutine cancellation does not produce a spurious error state.
68-146: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract aggregation logic from
loadJuiceData()for testability.
loadJuiceData()mixes network orchestration, health-score computation, language-breakdown math, and state mapping in a single function. Extract the health-score and language-breakdown calculations (lines 89-112) into standalone pure functions. This lets you unit test the scoring logic without mocking the network APIs.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceViewModel.kt` around lines 68 - 146, Extract the health-score calculation and language-breakdown computation from loadJuiceData() into standalone pure functions that accept repository data and return their respective results. Replace the inline logic in loadJuiceData() with calls to these functions, preserving the existing empty-repository behavior, scoring formula, and percentage calculations so the logic can be unit tested without network dependencies.app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceScreen.kt (2)
316-343: 📐 Maintainability & Code Quality | 🔵 TrivialTrack the placeholder action buttons as follow-up work.
Star,Watch,Fork,Clone, andBrowserall have empty/* Handle X */bodies. They render as active, clickable buttons that do nothing when tapped, which can confuse users. Do you want me to open a follow-up issue to implement these actions, or wire at least one (for example "Browser" opening the repo URL) in this PR?🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceScreen.kt` around lines 316 - 343, The RepositoryCardWithActions composable exposes nonfunctional Star, Watch, Fork, Clone, and Browser buttons; either implement their actions—prioritizing Browser to open repo.url—or disable/remove the placeholder buttons until functionality exists, and track any deferred actions as follow-up work.
163-172: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winAdd stable keys to
items()in the trending/repositoryLazyRowlists.
items(trendingRepos),items(updatedRepos),items(starredRepos), anditems(savedRepos)do not pass akey. Without a key, Compose falls back to positional identity, which can cause unnecessary recomposition or loss of item state when the underlying lists change. UseGitHubRepositoryModel's identity (for examplerepo.idor"${repo.owner.login}/${repo.name}") as the key.♻️ Example fix
- items(trendingRepos) { repo -> + items(trendingRepos, key = { it.id }) { repo ->Also applies to: 281-310
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceScreen.kt` around lines 163 - 172, Add stable keys to the `items()` calls for `trendingRepos`, `updatedRepos`, `starredRepos`, and `savedRepos` in their `LazyRow` lists. Use each `GitHubRepositoryModel`’s stable identity, such as `repo.id` or the owner/name combination, while preserving the existing item content and layout.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceScreen.kt`:
- Line 320: Update the repository description Text in GitHubJuiceScreen to
remove the fixed Modifier.height(40.dp), keeping maxLines = 2 so wrapped text
can expand appropriately with increased font scaling; use heightIn only if a
minimum height is required.
- Around line 227-232: Update the languageBreakdown display in GitHubJuiceScreen
so each it.value percentage is rounded and formatted to one decimal place before
appending the percent sign, while preserving the existing language name and
comma-separated output.
- Around line 43-118: Update the GitHubJuiceScreen composable to consume
GitHubJuiceState.isLoading and error. Show a visible progress indicator while
isLoading is true, render the error message when error is present with a retry
action wired to the existing loadJuiceData mechanism, and keep the data sections
available for the normal loaded state.
In
`@app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceViewModel.kt`:
- Around line 80-81: Update the trending query in the ViewModel’s async
searchRepositories call to remove “sort:stars-desc” from the query string, and
pass the API parameters sort = "stars" and order = "desc" explicitly. Preserve
the created-after filter and perPage = 10.
---
Nitpick comments:
In
`@app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceScreen.kt`:
- Around line 316-343: The RepositoryCardWithActions composable exposes
nonfunctional Star, Watch, Fork, Clone, and Browser buttons; either implement
their actions—prioritizing Browser to open repo.url—or disable/remove the
placeholder buttons until functionality exists, and track any deferred actions
as follow-up work.
- Around line 163-172: Add stable keys to the `items()` calls for
`trendingRepos`, `updatedRepos`, `starredRepos`, and `savedRepos` in their
`LazyRow` lists. Use each `GitHubRepositoryModel`’s stable identity, such as
`repo.id` or the owner/name combination, while preserving the existing item
content and layout.
In
`@app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceViewModel.kt`:
- Around line 142-144: Update the exception handling in loadJuiceData so
CancellationException is rethrown or otherwise allowed to propagate before
handling other exceptions. Keep the existing _state error update for genuine
failures and ensure coroutine cancellation does not produce a spurious error
state.
- Around line 68-146: Extract the health-score calculation and
language-breakdown computation from loadJuiceData() into standalone pure
functions that accept repository data and return their respective results.
Replace the inline logic in loadJuiceData() with calls to these functions,
preserving the existing empty-repository behavior, scoring formula, and
percentage calculations so the logic can be unit tested without network
dependencies.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9f94ea80-1278-49f8-9739-c54fa16bf56e
📒 Files selected for processing (3)
app/src/main/java/com/sayanthrock/githubrock/ui/navigation/AppNavigation.ktapp/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceScreen.ktapp/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceViewModel.kt
| val state by viewModel.state.collectAsState() | ||
|
|
||
| Scaffold( | ||
| topBar = { | ||
| TopAppBar( | ||
| title = { Text("GitHub Juice") }, | ||
| colors = TopAppBarDefaults.topAppBarColors( | ||
| containerColor = MaterialTheme.colorScheme.background | ||
| ) | ||
| ) | ||
| } | ||
| ) { paddingValues -> | ||
| LazyColumn( | ||
| modifier = Modifier | ||
| .fillMaxSize() | ||
| .padding(paddingValues), | ||
| contentPadding = PaddingValues(16.dp), | ||
| verticalArrangement = Arrangement.spacedBy(16.dp) | ||
| ) { | ||
| item { | ||
| JuiceOverviewSection( | ||
| dailySummary = state.dailySummary, | ||
| healthScore = state.repositoryHealthScore, | ||
| commitActivity = state.commitActivity | ||
| ) | ||
| } | ||
| item { | ||
| JuiceStatusSection( | ||
| openIssues = state.openIssuesSummary, | ||
| pullRequests = state.pullRequestStatus, | ||
| workflowStatus = state.workflowStatus, | ||
| recentReleases = state.recentReleases | ||
| ) | ||
| } | ||
| item { | ||
| JuiceTrendingSection( | ||
| trendingRepos = state.trendingRepositories, | ||
| trendingDevs = state.trendingDevelopers | ||
| ) | ||
| } | ||
| item { | ||
| JuiceGrowthSection( | ||
| repoGrowth = state.repositoryGrowth, | ||
| starGrowth = state.starGrowth, | ||
| forkGrowth = state.forkGrowth | ||
| ) | ||
| } | ||
| item { | ||
| JuiceContributorsSection( | ||
| topContributors = state.topContributors, | ||
| recentContributors = state.recentContributors, | ||
| commitStreak = state.commitStreak | ||
| ) | ||
| } | ||
| item { | ||
| JuiceCodeStatsSection( | ||
| languageBreakdown = state.languageBreakdown, | ||
| repoSize = state.repositorySize, | ||
| license = state.licenseDetection, | ||
| readmeStatus = state.readmeStatus, | ||
| latestTags = state.latestTags, | ||
| securityAdvisories = state.securityAdvisories, | ||
| codeFreq = state.codeFrequency, | ||
| timeline = state.activityTimeline | ||
| ) | ||
| } | ||
| item { | ||
| JuiceListsSection( | ||
| leaderboard = state.contributorLeaderboard, | ||
| updatedRepos = state.recentlyUpdatedRepositories, | ||
| starredRepos = state.recentlyStarredRepositories, | ||
| savedRepos = state.savedRepositories | ||
| ) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Surface state.isLoading and state.error in the UI.
GitHubJuiceState exposes isLoading and error, but this screen never reads either field. While isLoading is true, the user only sees the hardcoded "Loading..." placeholder strings baked into the default state — no progress indicator. If loadJuiceData() fails, error is set but never rendered, so the user has no feedback and no way to retry; the screen silently shows stale placeholder text forever.
🐛 Proposed fix (loading indicator + error banner)
) { paddingValues ->
+ if (state.isLoading) {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ CircularProgressIndicator()
+ }
+ return@Scaffold
+ }
+ state.error?.let { message ->
+ Box(Modifier.fillMaxSize().padding(16.dp)) {
+ Text(text = message, color = MaterialTheme.colorScheme.error)
+ }
+ }
LazyColumn(📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| val state by viewModel.state.collectAsState() | |
| Scaffold( | |
| topBar = { | |
| TopAppBar( | |
| title = { Text("GitHub Juice") }, | |
| colors = TopAppBarDefaults.topAppBarColors( | |
| containerColor = MaterialTheme.colorScheme.background | |
| ) | |
| ) | |
| } | |
| ) { paddingValues -> | |
| LazyColumn( | |
| modifier = Modifier | |
| .fillMaxSize() | |
| .padding(paddingValues), | |
| contentPadding = PaddingValues(16.dp), | |
| verticalArrangement = Arrangement.spacedBy(16.dp) | |
| ) { | |
| item { | |
| JuiceOverviewSection( | |
| dailySummary = state.dailySummary, | |
| healthScore = state.repositoryHealthScore, | |
| commitActivity = state.commitActivity | |
| ) | |
| } | |
| item { | |
| JuiceStatusSection( | |
| openIssues = state.openIssuesSummary, | |
| pullRequests = state.pullRequestStatus, | |
| workflowStatus = state.workflowStatus, | |
| recentReleases = state.recentReleases | |
| ) | |
| } | |
| item { | |
| JuiceTrendingSection( | |
| trendingRepos = state.trendingRepositories, | |
| trendingDevs = state.trendingDevelopers | |
| ) | |
| } | |
| item { | |
| JuiceGrowthSection( | |
| repoGrowth = state.repositoryGrowth, | |
| starGrowth = state.starGrowth, | |
| forkGrowth = state.forkGrowth | |
| ) | |
| } | |
| item { | |
| JuiceContributorsSection( | |
| topContributors = state.topContributors, | |
| recentContributors = state.recentContributors, | |
| commitStreak = state.commitStreak | |
| ) | |
| } | |
| item { | |
| JuiceCodeStatsSection( | |
| languageBreakdown = state.languageBreakdown, | |
| repoSize = state.repositorySize, | |
| license = state.licenseDetection, | |
| readmeStatus = state.readmeStatus, | |
| latestTags = state.latestTags, | |
| securityAdvisories = state.securityAdvisories, | |
| codeFreq = state.codeFrequency, | |
| timeline = state.activityTimeline | |
| ) | |
| } | |
| item { | |
| JuiceListsSection( | |
| leaderboard = state.contributorLeaderboard, | |
| updatedRepos = state.recentlyUpdatedRepositories, | |
| starredRepos = state.recentlyStarredRepositories, | |
| savedRepos = state.savedRepositories | |
| ) | |
| } | |
| } | |
| } | |
| val state by viewModel.state.collectAsState() | |
| Scaffold( | |
| topBar = { | |
| TopAppBar( | |
| title = { Text("GitHub Juice") }, | |
| colors = TopAppBarDefaults.topAppBarColors( | |
| containerColor = MaterialTheme.colorScheme.background | |
| ) | |
| ) | |
| } | |
| ) { paddingValues -> | |
| if (state.isLoading) { | |
| Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { | |
| CircularProgressIndicator() | |
| } | |
| return@Scaffold | |
| } | |
| state.error?.let { message -> | |
| Box(Modifier.fillMaxSize().padding(16.dp)) { | |
| Text(text = message, color = MaterialTheme.colorScheme.error) | |
| } | |
| } | |
| LazyColumn( | |
| modifier = Modifier | |
| .fillMaxSize() | |
| .padding(paddingValues), | |
| contentPadding = PaddingValues(16.dp), | |
| verticalArrangement = Arrangement.spacedBy(16.dp) | |
| ) { | |
| item { | |
| JuiceOverviewSection( | |
| dailySummary = state.dailySummary, | |
| healthScore = state.repositoryHealthScore, | |
| commitActivity = state.commitActivity | |
| ) | |
| } | |
| item { | |
| JuiceStatusSection( | |
| openIssues = state.openIssuesSummary, | |
| pullRequests = state.pullRequestStatus, | |
| workflowStatus = state.workflowStatus, | |
| recentReleases = state.recentReleases | |
| ) | |
| } | |
| item { | |
| JuiceTrendingSection( | |
| trendingRepos = state.trendingRepositories, | |
| trendingDevs = state.trendingDevelopers | |
| ) | |
| } | |
| item { | |
| JuiceGrowthSection( | |
| repoGrowth = state.repositoryGrowth, | |
| starGrowth = state.starGrowth, | |
| forkGrowth = state.forkGrowth | |
| ) | |
| } | |
| item { | |
| JuiceContributorsSection( | |
| topContributors = state.topContributors, | |
| recentContributors = state.recentContributors, | |
| commitStreak = state.commitStreak | |
| ) | |
| } | |
| item { | |
| JuiceCodeStatsSection( | |
| languageBreakdown = state.languageBreakdown, | |
| repoSize = state.repositorySize, | |
| license = state.licenseDetection, | |
| readmeStatus = state.readmeStatus, | |
| latestTags = state.latestTags, | |
| securityAdvisories = state.securityAdvisories, | |
| codeFreq = state.codeFrequency, | |
| timeline = state.activityTimeline | |
| ) | |
| } | |
| item { | |
| JuiceListsSection( | |
| leaderboard = state.contributorLeaderboard, | |
| updatedRepos = state.recentlyUpdatedRepositories, | |
| starredRepos = state.recentlyStarredRepositories, | |
| savedRepos = state.savedRepositories | |
| ) | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceScreen.kt`
around lines 43 - 118, Update the GitHubJuiceScreen composable to consume
GitHubJuiceState.isLoading and error. Show a visible progress indicator while
isLoading is true, render the error message when error is present with a retry
action wired to the existing loadJuiceData mechanism, and keep the data sections
available for the normal loaded state.
| Text(text = "Languages:", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary) | ||
| if (languageBreakdown.isEmpty()) { | ||
| Text(text = "No language data", style = MaterialTheme.typography.bodySmall) | ||
| } else { | ||
| Text(text = languageBreakdown.entries.joinToString(", ") { "${it.key}: ${it.value}%" }, style = MaterialTheme.typography.bodySmall) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Format the language-breakdown percentage before display.
"${it.key}: ${it.value}%" prints the raw Double, which can render with many decimal places (for example Kotlin: 45.83333333333333%). Round to one decimal place for readability.
💚 Proposed fix
- Text(text = languageBreakdown.entries.joinToString(", ") { "${it.key}: ${it.value}%" }, style = MaterialTheme.typography.bodySmall)
+ Text(
+ text = languageBreakdown.entries.joinToString(", ") { "${it.key}: ${"%.1f".format(it.value)}%" },
+ style = MaterialTheme.typography.bodySmall
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Text(text = "Languages:", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary) | |
| if (languageBreakdown.isEmpty()) { | |
| Text(text = "No language data", style = MaterialTheme.typography.bodySmall) | |
| } else { | |
| Text(text = languageBreakdown.entries.joinToString(", ") { "${it.key}: ${it.value}%" }, style = MaterialTheme.typography.bodySmall) | |
| } | |
| Text(text = "Languages:", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary) | |
| if (languageBreakdown.isEmpty()) { | |
| Text(text = "No language data", style = MaterialTheme.typography.bodySmall) | |
| } else { | |
| Text( | |
| text = languageBreakdown.entries.joinToString(", ") { "${it.key}: ${"%.1f".format(it.value)}%" }, | |
| style = MaterialTheme.typography.bodySmall | |
| ) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceScreen.kt`
around lines 227 - 232, Update the languageBreakdown display in
GitHubJuiceScreen so each it.value percentage is rounded and formatted to one
decimal place before appending the percent sign, while preserving the existing
language name and comma-separated output.
| ElevatedCard(modifier = Modifier.width(240.dp).padding(4.dp)) { | ||
| Column(modifier = Modifier.padding(12.dp)) { | ||
| Text(text = repo.name, style = MaterialTheme.typography.titleSmall, maxLines = 1) | ||
| Text(text = repo.description ?: "No description", style = MaterialTheme.typography.bodySmall, maxLines = 2, modifier = Modifier.height(40.dp)) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Avoid a fixed height combined with maxLines for wrapped description text.
Modifier.height(40.dp) combined with maxLines = 2 can clip the description when the user increases the system font scale, because the fixed height no longer matches two lines of larger text. Rely on maxLines alone, or use heightIn(min = ...) instead of a fixed height.
💚 Proposed fix
- Text(text = repo.description ?: "No description", style = MaterialTheme.typography.bodySmall, maxLines = 2, modifier = Modifier.height(40.dp))
+ Text(text = repo.description ?: "No description", style = MaterialTheme.typography.bodySmall, maxLines = 2)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Text(text = repo.description ?: "No description", style = MaterialTheme.typography.bodySmall, maxLines = 2, modifier = Modifier.height(40.dp)) | |
| Text(text = repo.description ?: "No description", style = MaterialTheme.typography.bodySmall, maxLines = 2) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceScreen.kt`
at line 320, Update the repository description Text in GitHubJuiceScreen to
remove the fixed Modifier.height(40.dp), keeping maxLines = 2 so wrapped text
can expand appropriately with increased font scaling; use heightIn only if a
minimum height is required.
| val trendingQuery = "created:>$oneWeekAgo sort:stars-desc" | ||
| val trendingReposDeferred = async { gitHubApi.searchRepositories(query = trendingQuery, perPage = 10) } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Does the GitHub REST search repositories API accept a sort qualifier inside the q parameter, or only as a separate sort parameter?
💡 Result:
The GitHub REST API for searching repositories requires the use of the explicit sort parameter rather than a qualifier within the q parameter [1][2][3]. While the GitHub web interface allows users to add a sort qualifier (e.g., sort:updated) directly into the search query string [4], the GitHub REST API search endpoints are distinct and strictly define sort and order as separate, top-level request parameters [1][2][3]. When using the GET /search/repositories endpoint, you must specify the desired sort field (such as stars, forks, or updated) using the sort query parameter. If you attempt to include a sort qualifier inside the q parameter, it will not be interpreted as a command to reorder the API response, as the API expects specific, documented parameters to control the response sort order [1][5].
Citations:
- 1: https://docs.github.com/en/rest/search/search?apiVersion=2022-1128
- 2: https://developer.github.com/enterprise/2.8/v3/search/
- 3: https://developer.github.com/enterprise/2.2/v3/search/
- 4: https://docs.github.com/en/search-github/getting-started-with-searching-on-github/sorting-search-results
- 5: https://stackoverflow.com/questions/40762518/github-api-sorting-search-results-by-the-created-at-field
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and inspect the relevant ViewModel and API definitions without executing repo code.
printf 'Files matching GitHubJuiceViewModel / searchRepositories:\n'
fd -a 'GitHubJuiceViewModel|.*Api.*|.*Repository.*' . | sed 's#^\./##' | head -80
printf '\nSearch for searchRepositories definitions/usages:\n'
rg -n "searchRepositories|fun searchRepositories|suspend fun searchRepositories|data class search|sort.*order|created:>" .
printf '\nRead candidate ViewModel section:\n'
file=$(fd 'GitHubJuiceViewModel.kt' . | head -1 || true)
if [ -n "${file:-}" ]; then
wc -l "$file"
sed -n '1,140p' "$file" | cat -n
fi
printf '\nRead candidate gitHubApi definitions:\n'
for f in $(rg -l "interface .*Api|class .*Api|object .*Api|GitHubApi" .); do
echo "--- $f"
rg -n "interface|class|object|searchRepositories|perPage|sort|order|github|api" "$f" | head -120
doneRepository: Sayanthrock-Developer/GitHub-Rock
Length of output: 21668
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and inspect the relevant ViewModel and API definitions without executing repo code.
printf 'Files matching GitHubJuiceViewModel / searchRepositories:\n'
fd -a 'GitHubJuiceViewModel|.*Api.*|.*Repository.*' . | sed 's#^\./##' | head -80
printf '\nSearch for searchRepositories definitions/usages:\n'
rg -n "searchRepositories|fun searchRepositories|suspend fun searchRepositories|data class search|sort.*order|created:>" .
printf '\nRead candidate ViewModel section:\n'
file=$(fd 'GitHubJuiceViewModel.kt' . | head -1 || true)
if [ -n "${file:-}" ]; then
wc -l "$file"
sed -n '1,140p' "$file" | cat -n
fi
printf '\nRead candidate Git API definitions/usages:\n'
rg -l "interface .*Api|class .*Api|object .*Api|GitHubApi|`@GET`|`@Query`|searchRepositories" . | while read -r f; do
echo "--- $f"
wc -l "$f"
rg -n "interface|class|object|`@GET`|`@Query`|query|perPage|sort|order|searchRepositories|GitHubApi|baseUrl|`@Path`|search_repositories|search/github" "$f" | sed -n '1,180p'
doneRepository: Sayanthrock-Developer/GitHub-Rock
Length of output: 29391
Use the sort/order API parameters for the trending query.
searchRepositories(...) already exposes sort and order as Retrofit query parameters, but this call keeps sort:stars-desc inside the q value and relies on the default sort = "updated", so the results are not sorted by stars. Move the ordering out of q and pass sort = "stars", order = "desc".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceViewModel.kt`
around lines 80 - 81, Update the trending query in the ViewModel’s async
searchRepositories call to remove “sort:stars-desc” from the query string, and
pass the API parameters sort = "stars" and order = "desc" explicitly. Preserve
the created-after filter and perPage = 10.
User description
Implemented a new "GitHub Juice" section to provide powerful developer insights using only free GitHub APIs.
searchRepositoriesto fetch trending repositories created within the last 7 days sorted by stars.GitHubJuiceScreenwith Jetpack Compose usingElevatedCard,LazyRow, and typography aligned with the app's dark theme design.JuiceintoTopDestinationso it's readily accessible from the app's primary navigation bar.hasLoadedflag to the ViewModel state flow to ensure data is fetched once and retained across recompositions.PR created automatically by Jules for task 13257896304218001888 started by @SayanthRock
CodeAnt-AI Description
Add a GitHub Juice dashboard for repository and developer insights
What Changed
Impact
✅ Centralized GitHub activity overview✅ Faster access to trending repositories✅ Clearer repository health and growth insights💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.
Summary by CodeRabbit